package ij.plugin;

import ij.ImagePlus;


import ij.IJ;
import ij.ImageStack;
import ij.WindowManager;
import ij.measure.ResultsTable;
import ij.plugin.filter.Axes3D_;
import ij.process.ImageProcessor;
import ij.process.ShortProcessor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
/**
 * Entré: deux Image en 16 bits
 *  Extraction des objects
 *  Tester le contact entre les objects de deux images ainsi que les pourcentages de contact
 *  Construction de l'object "contact"
 *  Calcule les Centres de gravité de tous les Object
 *  Calcule les distances entre Objects
 * @author     MAROUEN BOUAZZI
 * @created    Mars 2011
 */
public class Mereotopologie3D implements PlugIn {
	ArrayList<ArrayList[]> alAllObjects=new ArrayList<ArrayList[]>();
	int nbSlices=0;
	Vector<Integer> nbObject;
	Vector<Voxel> vPixelsContact;
	double [][] tabPixCommun;
	double [][] tabPourcentage1;
	double [][] tabPourcentage2;
	double [][] tabDistance1;
	double [][] tabDistance2;
	ArrayList[] tabObject1;
	ArrayList[] tabObject2;
	HashMap<String, ArrayList<Voxel>> hmObjectsContact;
	HashMap<String, Integer> vDoubleContact=new HashMap<String, Integer>();
	int width,height;

	VoxelCentroid[] tabPixelCentroid1,tabPixelCentroid2;
	HashMap<String, VoxelCentroid> hashmMapPixelCentroidObjectCommun;
	ImageProcessor ipp;
	ImageProcessor ipp1;
	ImagePlus imp;
	Axes3D_ ax,ax2;
	public void run(String str) {
		nbObject=new Vector<Integer>();
		int ids []= WindowManager.getIDList();
		ImageStack imageStack;
		Vector<ImageStack> StackImage=new Vector<ImageStack>();
		if(ids.length>=2){
			for (int i = 0; i < ids.length; i++) {


				 imp=WindowManager.getImage(ids[i]);
				 ipp=imp.getProcessor();
				width=ipp.getWidth();
				height=ipp.getHeight();
				if(ipp instanceof ShortProcessor){
					 ipp1=imp.getProcessor();
					ax=new Axes3D_(imp);
					 ax.run(ipp1);
					
					imageStack=imp.getImageStack();
					nbSlices=imageStack.getSize();
					StackImage.add(imageStack);
					alAllObjects.add(ExtractObjects(imageStack));

				}
			}
			if(alAllObjects.size()==2){
				tabObject1=alAllObjects.get(0);
				tabObject2=alAllObjects.get(1);

				vPixelsContact=getPixelsContactForImages(StackImage.get(0), StackImage.get(1));

				ConstructTableForContact(vPixelsContact);

				/*ShowTableForTab2D(nbObject.get(1), nbObject.get(0), tabPixCommun, "Mereologie");
				ShowTableForTab2D(nbObject.get(1), nbObject.get(0), tabPourcentage1, "Pourcentage de contact entre les objects / 2");
				ShowTableForTab2D(nbObject.get(1), nbObject.get(0), tabPourcentage2, "Pourcentage de contact entre les objects / 1");*/

				tabPixelCentroid1=getCentroid(tabObject1);
				tabPixelCentroid2=getCentroid(tabObject2);

				hmObjectsContact=getObjectForPixelsContact(vPixelsContact);
				hashmMapPixelCentroidObjectCommun=getCentroidForObjectCommun(hmObjectsContact);
				
				tabDistance1=new double [nbObject.get(0)][nbObject.get(1)];
				tabDistance2=new double [nbObject.get(1)][nbObject.get(0)];
				CalculeDistance(hmObjectsContact);
				
				/*ShowTableForTab2D(nbObject.get(1), nbObject.get(0), tabDistance1, "Distance de contact entre les objects / 2");
				ShowTableForTab2D(nbObject.get(0), nbObject.get(1), tabDistance2, "Distance de contact entre les objects / 1");*/
				
				//**Axes3D_ ax=new Axes3D_(imp);
				//**ax.run(ipp1);
				Reset();
			}else IJ.error("Type (16 bit)", "Image ---> Type ---> 16 bits ");
		}else  IJ.error("Ressources", "Nombre d'images insuffisant, min=2 (16 bit) ");



	}
	private void Reset(){
		alAllObjects=new ArrayList<ArrayList[]>();
	}
	/*
	 * Affiche les Tables de resultat
	 */
	private void ShowTableForTab2D(int nbObject1,int nbObject0,double [][] tab,String title){
		ResultsTable rt=new ResultsTable();
		for (int i = 1; i < nbObject1; i++) {
			rt.incrementCounter();
			for (int j = 1; j < nbObject0; j++) {

				rt.addValue("obj"+j, tab[j][i]);
			}
		}

		rt.show(title);
	}
	/*/#!/permalink.php?story_fbid=1872566462355&id=1486953372&notif_t=feed_comment
	 * Extraire les differentes objects et les mettre dans une Tableau des ArrayList (1<tab.lengtn<val)
	 */
	private ArrayList[] ExtractObjects(ImageStack imageStack ) {
		int k=1;
		int number=getNumberObject(imageStack);

		nbObject.add(number+1);
		ArrayList<Voxel> [] tabObjects=new ArrayList[number+1];
		for (int i = 0; i < tabObjects.length; i++) {
			tabObjects[i]=new ArrayList<Voxel>();
		}
		while(k<=nbSlices){
			ImageProcessor ip=imageStack.getProcessor(k);
			short pixels[]=(short[]) ip.getPixels();
			for (int i = 0; i < pixels.length; i++) {
				int val=pixels[i];
				if(val!=0){
					tabObjects[val].add(new Voxel(k,i,val));
				}
			}
			k++;
		}
		return tabObjects;
	}
	/*
	 * Calcule le nombre d'object dans une ImageStack
	 */
	private int getNumberObject(ImageStack imageStack){
		int k=1;
		int max=0;
		while(k<=imageStack.getSize()){
			ImageProcessor ip=imageStack.getProcessor(k);
			short pixels[]=(short[]) ip.getPixels();

			for (int i = 0; i < pixels.length; i++) {
				int val=pixels[i];
				if(val>max)
					max=val;
			}
			k++;
		}
		return max;
	}
	/*
	 * Chercher les pixels en contact entre les objects de deux ImageStack, et les mettres dans un vecteur de PixelContact
	 */

	private Vector<Voxel> getPixelsContactForImages(ImageStack imageStack1,ImageStack imageStack2){
		int k=1;
		Vector<Voxel> v=new Vector<Voxel>();
		while(k<=nbSlices){
			ImageProcessor ip1=imageStack1.getProcessor(k);
			ImageProcessor ip2=imageStack2.getProcessor(k);
			short pixels1[]=(short[]) ip1.getPixels();
			short pixels2[]=(short[]) ip2.getPixels();

			for (int i = 0; i < pixels1.length; i++) {
				int val=pixels1[i];
				int val2=pixels2[i];

				if((val!=0)&&(val2!=0)){
					v.add(new Voxel(k,i, val,val2));
				}
			}
			k++;
		}
		return v;
	}
	/*
	 * Remplir une Tableau à 2 dimention par le nombre de pixels en contact entre deux object
	 * et calculer les pourcentages de contact de l'ImageStack 1 par rapport à l'autre, et vice versa 
	 */
	private void ConstructTableForContact(Vector<Voxel> v){
		int size1=nbObject.get(0);
		int size2=nbObject.get(1);
		tabPixCommun=new double[size1][size2];
		tabPourcentage1=new double[size1][size2];
		tabPourcentage2=new double[size1][size2];

		int i=0;
		while(i<v.size()){
			Voxel p=v.get(i);
			int val1=p.getValue1();
			int val2=p.getValue2();
			tabPixCommun[val1][val2]=tabPixCommun[val1][val2]+1d;
			i++;
		}

		for (int j = 1; j < size2; j++) {
			for (int j2 = 1; j2 < size1; j2++) {
				tabPourcentage1[j2][j]=(tabPixCommun[j2][j]/tabObject1[j2].size())*100;
			}
		}

		for (int j = 1; j < size1; j++) {
			for (int j2 = 1; j2 < size2; j2++) {
				tabPourcentage2[j][j2]=(tabPixCommun[j][j2]/tabObject2[j2].size())*100;
			}
		}
	}
	/*
	 * Calcule les Centres de gravité des objects normaux, et les mettre dans une tableau de PixelCentroid
	 */
	public VoxelCentroid[] getCentroid(ArrayList[] tab){

		ArrayList<Voxel> al;
		VoxelCentroid[] tabPixelCentroid=new VoxelCentroid[tab.length];

		int nbPixel=0;
		for (int i = 1; i < tab.length; i++) {
			int moyX=0,moyY=0,moyZ=0;
			al=tab[i];
			nbPixel=al.size();
			Iterator<Voxel> itr = al.iterator(); 
			while(itr.hasNext()) {

				Voxel pixelObject = itr.next(); 

				moyX=moyX+pixelObject.getX();
				moyY=moyY+pixelObject.getY();
				moyZ=moyZ+pixelObject.getSlice();
			} 

			//**System.out.println("Centroid Object :"+i+"  X=  "+moyX/nbPixel+"  Y =  "+moyY/nbPixel+"  Z=  "+moyZ/nbPixel);
			tabPixelCentroid[i]=new VoxelCentroid(moyX/nbPixel, moyY/nbPixel, moyZ/nbPixel);
		}
		return tabPixelCentroid;
	}
	/*
	 * Calcule les Centres de gravité des objects contact, et les mettre dans une HashMap de PixelCentroid ou le key et la valeur de PixelContact de la premiere Image
	 */
	public HashMap<String, VoxelCentroid> getCentroidForObjectCommun(HashMap<String,ArrayList<Voxel>> hm){

		ArrayList<Voxel> al;
		HashMap< String, VoxelCentroid> hm1=new HashMap<String, VoxelCentroid>();
		int nbPixel=0;
		int i=0;
		Set<String> set=hm.keySet();
		Iterator<String> itr = set.iterator(); 
		while(itr.hasNext()) {
			int moyX=0,moyY=0,moyZ=0;
			String value=itr.next();
			al=hm.get(value);
			nbPixel=al.size();
			Iterator<Voxel> itr1 = al.iterator(); 
			while(itr1.hasNext()) {

				Voxel pixelContact = itr1.next(); 

				moyX=moyX+pixelContact.getX();
				moyY=moyY+pixelContact.getY();
				moyZ=moyZ+pixelContact.getSlice();
			} 

			//**System.out.println("Centroid Contact For Object:    "+value+"     X=  "+moyX/nbPixel+"  Y =  "+moyY/nbPixel+"  Z=  "+moyZ/nbPixel);
			hm1.put(value, new VoxelCentroid(moyX/nbPixel, moyY/nbPixel, moyZ/nbPixel));
			i++;
		}

		return hm1;
	}
	/*
	 * Réorganiser les pixels en contact et les rangé par valeur(Construction des Objects Contact)
	 * S'il y a plus d'un object en contact avec un autre, on les change leurs valeur, et en garde une une indice dans vDoubleContact
	 */
	public HashMap<String, ArrayList<Voxel>> getObjectForPixelsContact(Vector<Voxel> vPixelsContact){

		int nb=0;
		for (int i = 1; i <nbObject.get(0); i++) {
			for (int j = 1; j < nbObject.get(1); j++) {
				if(tabPixCommun[i][j]!=0)
					nb++;
			}
		}
		HashMap<String, ArrayList<Voxel>> hm=new HashMap<String, ArrayList<Voxel>>(nb);
		int k=0;
		int l=0;
		boolean trouve = false;
		while(k<vPixelsContact.size()){
			Voxel pixelContact=vPixelsContact.get(k);
			int val1=pixelContact.getValue1();
			int val2=pixelContact.getValue2();

			if(hm.containsKey(""+val1)){
				trouve = false;
				if(hm.get(""+val1).get(0).getValue2()==val2){
					ArrayList<Voxel> al1=hm.get(""+val1);
					al1.add(pixelContact);
					hm.put(""+val1, al1);
					trouve=true;
				}else{
					Set set=vDoubleContact.keySet();
					Iterator<String> itr = set.iterator(); 
					while(itr.hasNext()) {
						String ss=itr.next();

						if(ss.startsWith(""+val1)){
							int v2=vDoubleContact.get(ss);
							if(val2==v2){
								ArrayList<Voxel> al1=hm.get(ss);
								al1.add(pixelContact);
								hm.put(ss, al1);
								trouve=true;
								break;
							}
						}
					}
				}
				if(!trouve){
					l++;
					ArrayList<Voxel> al2=new ArrayList<Voxel>();
					al2.add(pixelContact);
					String s="_"+(getIndice(""+val1)+1);
					String s1=""+val1;
					hm.put(s1.concat(s), al2);
					vDoubleContact.put(s1.concat(s), val2);
				}

			}else {
				ArrayList<Voxel> al1=new ArrayList<Voxel>();
				al1.add(pixelContact);
				hm.put(""+val1, al1);
			}
			k++;

		}

		return hm;
	}

	/*
	 * Calcule de les distance entre les centres de gravités des objects en contact avec celles de l'object contact 
	 */
	private void CalculeDistance(HashMap<String, ArrayList<Voxel>> hmObjectsContact){
		VoxelCentroid pc = null;
		VoxelCentroid pc1 = null;
		VoxelCentroid pc2 = null;
		Set<String> set=hmObjectsContact.keySet();
		Iterator<String> itr = set.iterator(); 
		//**tabDistance1=new double [][];
		while(itr.hasNext()) {
			boolean trouve=false;
			String key=itr.next();
			ArrayList<Voxel> al=	hmObjectsContact.get(key);
			Voxel pixelContact=al.get(0);
			int val1=pixelContact.getValue1();
			int val2=pixelContact.getValue2();

			Set<String> set1=vDoubleContact.keySet();
			Iterator<String> itr1 = set1.iterator(); 
			while(itr1.hasNext()) {
				String key1=itr1.next();
				if(key1.startsWith(""+val1)){
					if(val2==vDoubleContact.get(key1)){
						pc=hashmMapPixelCentroidObjectCommun.get(key1);
						pc1=tabPixelCentroid1[val1];
						pc2=tabPixelCentroid2[val2];
						vDoubleContact.remove(key1);
						trouve=true;
						break;
					}
				}
			}


			if(!trouve){
				pc=hashmMapPixelCentroidObjectCommun.get(""+val1);
				pc1=tabPixelCentroid1[val1];
				pc2=tabPixelCentroid2[val2];
			}

			double distance1=Math.sqrt((pc1.getX()-pc.getX())*(pc1.getX()-pc.getX()) + (pc1.getY()-pc.getY())*(pc1.getY()-pc.getY()) + (pc1.getZ()-pc.getZ())*(pc1.getZ()-pc.getZ()));
			double distance2=Math.sqrt((pc2.getX()-pc.getX())*(pc2.getX()-pc.getX()) + (pc2.getY()-pc.getY())*(pc2.getY()-pc.getY()) + (pc2.getZ()-pc.getZ())*(pc2.getZ()-pc.getZ()));
			
			tabDistance1[val1][val2]=distance1;
			tabDistance2[val2][val1]=distance2;
			
			//**System.out.println("distance1 == "+distance1+"  distance2  == "+distance2);
		}



	}
	private int getIndice(String val1){
		int max=0;
		Set<String> set1=vDoubleContact.keySet();
		Iterator<String> itr1 = set1.iterator(); 
		while(itr1.hasNext()) {
			String val=itr1.next();
			if(val.startsWith(""+val1)){
				String sss=val.substring(val.indexOf("_")+1);
				int a=Integer.parseInt(sss);
				if(a>max)
					max=a;
			}
		}
		return max;
	}

	class VoxelCentroid{
		int x,y,z;
		VoxelCentroid(int x,int y,int z){
			this.x=x;
			this.y=y;
			this.z=z;
		}

		int getX(){
			return x;
		}
		int getY(){
			return y;
		}
		int getZ(){
			return z;
		}
	}
	
	class Voxel{
		
		int x,y,coord,value,val2,slice;
		Voxel(int slice,int coord,int value){
			this.coord=coord;
			this.value=value;
			this.slice=slice;
			this.x=coord % width;
			this.y=coord / width;
		}
		
		public Voxel(int slice,int coord,int valOrig,int valCont) {
			this.coord=coord;
			this.value=valOrig;
			this.val2=valCont;
			this.slice=slice;
			this.x=coord % width;
			this.y=coord / width;
		}
		int getCoord(){
			return coord;
		}

		int getValue1(){
			return value;
		}
		int getValue2(){
			return val2;
		}
		int getSlice(){
			return slice;
		}
		int getX(){
			return x;
		}
		int getY(){
			return y;
		}
		
	}
}
